<?php
/**
 * @link https://ym2.cleverstone.top
 * @copyright Copyright (c) 2020 Yii Manager Software LLC
 */

namespace builder\uploads;

use builder\form\FormBuilder;
use builder\helper\App;
use builder\helper\H5;
use builder\helper\T;
use builder\helper\Url;
use builder\base\BaseController;
use Yii;
use yii\base\DynamicModel;
use yii\base\ErrorException;
use yii\helpers\FileHelper;

/**
 * 文件上传
 * @author cleverstone
 * @since ym2.0
 */
class UploadController extends BaseController
{
    /**
     * @var array 访问动作限制
     */
    public $actionVerbs = [
        'classify-add' => ['GET', 'POST'],
        'classify-edit' => ['GET', 'POST'],
        'classify-delete' => ['POST'],
        'classify' => ['GET'],
        'file-transfer' => ['GET', 'POST'],
        'file-edit' => ['GET', 'POST'],
        'file-delete' => ['POST'],
        'index' => ['GET'],
        'resume' => ['GET'],
        'chunk' => ['GET', 'POST'],
        'move' => ['POST'],
        'cloud-file' => ['post'],
    ];
    public $undetectedActions = [
        'classify-add',
        'classify-edit',
        'classify-delete',
        'classify',
        'file-transfer',
        'file-edit',
        'file-delete',
        'index',
        'resume',
        'chunk',
        'move',
        'cloud-file',
    ];

    /**
     * @var string 临时目录
     */
    public $tmpDir = '@root/tmp';
    /**
     * @var string 资源标签标识参数
     */
    public $etagParam = 'etag';
    /**
     * @var string 文件字段
     */
    private $_fileParam;

    /**
     * Init
     * @throws \yii\base\Exception
     */
    public function init()
    {
        parent::init();

        $this->_fileParam = $this->uploadManager->provider->fileParam;

        $this->tmpDir = rtrim(Yii::getAlias($this->tmpDir), '/') . '/';
        if (!is_dir($this->tmpDir)) {
            FileHelper::createDirectory($this->tmpDir);
        }
    }

    /**
     * 重置视图目录路径
     * @return string
     */
    public function getViewPath()
    {
        return __DIR__ . '/views';
    }

    /**
     * 云文件上传
     * @return string
     * @throws \Throwable
     * @throws \yii\base\InvalidConfigException
     */
    public function actionCloudFile()
    {
        $uploadManager = $this->uploadManager;
        $bodyParams = $this->filteredPost;
        $dynamic = new DynamicModel($bodyParams);
        $boolean = $dynamic->addRule(['file', 'classify'], 'required')
            ->addRule('classify', 'integer')
            ->addRule('file', 'string', ['max' => 500])
            ->setAttributeLabels([
                'file' => '文件链接地址',
                'classify' => '上传分组',
            ])
            ->validate();
        if (!$boolean) {
            return $this->asFail(reset($dynamic->firstErrors));
        }

        $classify = $bodyParams['classify'];
        $cloudLink = $bodyParams['file'];
        try {
            $content = file_get_contents($cloudLink);
            $tmpPath = $this->tmpDir . md5($cloudLink);
            $boolean = file_put_contents($tmpPath, $content);
            if ($boolean === false) {
                return $this->asFail('创建临时文件失败');
            }
        } catch (\Exception $e) {
            return $this->asFail('链接地址错误: ' . $e->getMessage());
        }

        $filename = pathinfo($cloudLink, PATHINFO_BASENAME);
        $_FILES[$this->_fileParam] = [
            'name' => $filename,
            'type' => FileHelper::getMimeType($tmpPath, null, false),
            'tmp_name' => $tmpPath,
            'error' => 0,
            'size' => filesize($tmpPath),
        ];
        $result = $uploadManager->add($classify, false);
        @unlink($tmpPath);

        if ($result) {
            return $this->asOk('', $uploadManager->result);
        }

        return $this->asFail($uploadManager->error);
    }

    /**
     * 删除文件
     * @return string
     * @throws \yii\db\Exception
     */
    public function actionFileDelete()
    {
        $uploadManager = $this->uploadManager;
        $bodyParam = $this->filteredPost;
        if (empty($bodyParam['id'])) {
            return $this->asFail('参数id缺失!');
        }

        $uploadManager->db->createCommand()->delete($uploadManager->fileTable, ['id' => $bodyParam['id']])->execute();
        return $this->asOk('删除成功');
    }

    /**
     * 删除目录
     * @return string
     * @throws \yii\db\Exception
     */
    public function actionClassifyDelete()
    {
        $uploadManager = $this->uploadManager;
        $bodyParam = $this->filteredPost;
        if (!isset($bodyParam['id'])) {
            return $this->asFail('id不可为空!');
        }

        if ($bodyParam['id'] == 0) {
            return $this->asFail('禁止删除 [全部] 目录!');
        }

        $idMap = $uploadManager->query->select('id')->from($uploadManager->fileClassifyTable)->where(['like', 'path', "-{$bodyParam['id']}-"])->column();
        array_unshift($idMap, $bodyParam['id']);
        $isNotEmpty = $uploadManager->query->select('id')->from($uploadManager->fileTable)->where(['classify_id' => $idMap])->one();
        if ($isNotEmpty) {
            return $this->asFail('当前目录非空,无法删除!');
        }

        $uploadManager->db->createCommand()->delete($uploadManager->fileClassifyTable, ['or', ['id' => $bodyParam['id']], ['like', 'path', "-{$bodyParam['id']}-"]])->execute();
        return $this->asOk('删除成功!');
    }

    /**
     * 编辑目录
     * @param int $id
     * @return string
     * @throws \Throwable
     * @throws \yii\base\InvalidConfigException
     * @throws \yii\db\Exception
     */
    public function actionClassifyEdit($id)
    {
        $uploadManager = $this->uploadManager;
        $classifyData = $uploadManager->query->from($uploadManager->fileClassifyTable)
            ->where(['id' => $id])
            ->one();
        if ($this->isPost) {
            if (empty($classifyData)) {
                return $this->asFail('id错误!');
            }

            $bodyParam = $this->filteredPost;
            $dynamic = new DynamicModel($bodyParam);
            $boolean = $dynamic->addRule(['id', 'name'], 'required')
                ->addRule('id', 'integer')
                ->addRule('name', 'string', ['max' => 10])
                ->setAttributeLabels([
                    'name' => '目录名称',
                    'id' => 'ID',
                ])
                ->validate();
            if (!$boolean) {
                return $this->asFail(reset($dynamic->firstErrors));
            }

            $uploadManager->db
                ->createCommand()
                ->update($uploadManager->fileClassifyTable, ['name' => $bodyParam['name']], ['id' => $bodyParam['id']])
                ->execute();

            return $this->asOk('编辑成功!');
        } else {
            if (empty($classifyData)) {
                return $this->renderContentX(H5::alert('禁止编辑该目录', 'info'));
            }

            $formBuilder = FormBuilder::instance();
            $formBuilder->setTitle('编辑名称')
                ->setPartial()
                ->setRequiredStyle()
                ->setAutoClose()
                ->setSubmittedCalls([
                    'parent' => ['classifyView'],
                ])
                ->setCloseBtn()
                ->setSubmitBtn('确定')
                ->setFormControl([
                    'id' => $this->formControl->hidden()->defaultValue($id),
                    'name' => $this->formControl->text()->label('名称')->required()->placeholder('请填写目录名称')->defaultValue($classifyData['name']),
                ]);
            return $formBuilder->render();
        }
    }

    /**
     * 新增分类目录
     * @param int $pid 父ID
     * @return string
     * @throws \yii\base\InvalidConfigException
     * @throws \yii\db\Exception
     */
    public function actionClassifyAdd($pid)
    {
        if ($this->isPost) {
            $bodyParam = $this->filteredPost;
            $dynamic = new DynamicModel($bodyParam);
            $boolean = $dynamic->addRule(['pid', 'name'], 'required')
                ->addRule('pid', 'integer')
                ->addRule('name', 'string', ['max' => 10])
                ->setAttributeLabels([
                    'name' => '目录名称',
                    'pid' => '父目录',
                ])
                ->validate();
            if (!$boolean) {
                return $this->asFail(reset($dynamic->firstErrors));
            }

            $uploadManager = $this->uploadManager;
            if ($bodyParam['pid'] != 0) {
                $one = $uploadManager->query->select('path')->from($uploadManager->fileClassifyTable)->where(['id' => $bodyParam['pid']])->one();
                $path = "{$one['path']}{$bodyParam['pid']}-";
            } else {
                $path = '-0-';
            }
            $data = [
                'pid' => $bodyParam['pid'],
                'name' => $bodyParam['name'],
                'path' => $path,
                'created_at' => T::now(),
                'updated_at' => T::now(),
            ];
            $uploadManager->db->createCommand()->insert($uploadManager->fileClassifyTable, $data)->execute();

            return $this->asOk('添加成功');
        } else {
            $formBuilder = FormBuilder::instance();
            $formBuilder->setTitle('新增目录')
                ->setPartial()
                ->setRequiredStyle()
                ->setAutoClose()
                ->setSubmittedCalls([
                    'parent' => ['classifyView'],
                ])
                ->setCloseBtn()
                ->setSubmitBtn('确定')
                ->setFormControl([
                    'pid' => $this->formControl->hidden()->defaultValue($pid),
                    'name' => $this->formControl->text()->label('名称')->required()->placeholder('请填写目录名称'),
                ]);
            return $formBuilder->render();
        }
    }

    /**
     * 分类目录
     * @return string
     */
    public function actionClassify()
    {
        $uploadManager = $this->uploadManager;
        $data = $uploadManager->query->from($uploadManager->fileClassifyTable)
            ->all();

        $result = $this->formatTreeRecursive($data);
        return $this->asOk('success', array_merge([
            [
                'title' => '全部',
                'id' => 0,
            ]
        ], $result));
    }

    /**
     * 递归格式化为树结构
     * @param array $data
     * @param int $pid
     * @return array
     */
    protected function formatTreeRecursive($data, $pid = 0)
    {
        $result = [];
        foreach ($data as $item) {
            if ($item['pid'] == $pid) {
                array_push($result, [
                    'title' => $item['name'],
                    'id' => $item['id'],
                    'spread' => true,
                    'children' => $this->formatTreeRecursive($data, $item['id']),
                ]);
            }
        }

        return $result;
    }

    /**
     * 递归格式化为下拉选项
     * @param array $data
     * @param int $pid
     * @param string $prefix
     * @return array
     */
    protected function formatOptionRecursive($data, $pid = 0, $prefix = '')
    {
        $result = [];
        foreach ($data as $item) {
            if ($item['pid'] == $pid) {
                $result[$item['id']] = $prefix . $item['name'];
                $result = $result + $this->formatOptionRecursive($data, $item['id'], $prefix . '|--');
            }
        }

        return $result;
    }

    /**
     * 文件转移
     * @param int $currentClassify 当前分类ID
     * @param array $idMap 文件ID集合
     * @return string
     * @throws \yii\base\InvalidConfigException
     * @throws \yii\db\Exception
     */
    public function actionFileTransfer($currentClassify, array $idMap)
    {
        if ($this->isPost) {
            $bodyParam = $this->filteredPost;
            $dynamic = new DynamicModel($bodyParam);
            $boolean = $dynamic->addRule(['classify'], 'required')
                ->addRule('classify', 'integer')
                ->setAttributeLabels(['classify' => '转移目录'])
                ->validate();
            if (!$boolean) {
                return $this->asFail(reset($dynamic->firstErrors));
            }

            $uploadManager = $this->uploadManager;
            $this->uploadManager->db
                ->createCommand()
                ->update($uploadManager->fileTable, ['classify_id' => $bodyParam['classify']], ['id' => $idMap])
                ->execute();

            return $this->asOk('转移成功!');
        } else {
            $uploadManager = $this->uploadManager;
            $data = $uploadManager->query->from($uploadManager->fileClassifyTable)
                ->all();
            $classifyOptions = ['0' => '全部'] + $this->formatOptionRecursive($data);
            $formBuilder = FormBuilder::instance();
            $formBuilder->setTitle('文件转移')
                ->setPartial()
                ->setRequiredStyle()
                ->setAutoClose()
                ->setSubmittedCalls([
                    'parent' => ['fileView'],
                ])
                ->setCloseBtn()
                ->setSubmitBtn('确定')
                ->setFormControl([
                    'classify' => $this->formControl->select()
                        ->label('文件目录')
                        ->options($classifyOptions)
                        ->placeholder('请选择移动到的目录')
                        ->required()->disabled([$currentClassify]),
                ]);
            return $formBuilder->render();
        }
    }

    /**
     * 文件编辑
     * @param int $id
     * @return string
     * @throws \Throwable
     * @throws \yii\base\InvalidConfigException
     * @throws \yii\db\Exception
     */
    public function actionFileEdit($id)
    {
        $uploadManager = $this->uploadManager;
        $fileData = $uploadManager->query->from($uploadManager->fileTable)
            ->where(['id' => $id])
            ->one();
        if ($this->isPost) {
            if (empty($fileData)) {
                return $this->asFail('id错误!');
            }

            $bodyParam = $this->filteredPost;
            $dynamic = new DynamicModel($bodyParam);
            $boolean = $dynamic->addRule(['id', 'name'], 'required')
                ->addRule('id', 'integer')
                ->addRule('name', 'string', ['max' => 255])
                ->setAttributeLabels([
                    'name' => '文件名',
                    'id' => 'ID',
                ])
                ->validate();
            if (!$boolean) {
                return $this->asFail(reset($dynamic->firstErrors));
            }

            $uploadManager->db
                ->createCommand()
                ->update($uploadManager->fileTable, ['name' => $bodyParam['name']], ['id' => $bodyParam['id']])
                ->execute();

            return $this->asOk('编辑成功!');
        } else {
            if (empty($fileData)) {
                return $this->renderContentX(H5::alert('参数id错误!', 'info'));
            }

            $formBuilder = FormBuilder::instance();
            $formBuilder->setTitle('编辑文件名')
                ->setPartial()
                ->setRequiredStyle()
                ->setAutoClose()
                ->setSubmittedCalls([
                    'parent' => ['fileView'],
                ])
                ->setCloseBtn()
                ->setSubmitBtn('确定')
                ->setFormControl([
                    'id' => $this->formControl->hidden()->defaultValue($id),
                    'name' => $this->formControl->text()->label('文件名')->required()->placeholder('请填写文件名')->defaultValue($fileData['name']),
                ]);
            return $formBuilder->render();
        }
    }

    /**
     * 文件管理
     * @return string
     * @throws \Throwable
     */
    public function actionIndex()
    {
        if ($this->isAjax) {
            $uploadManager = $this->uploadManager;
            $queryParam = $this->filteredGet;

            $query = $uploadManager->query->select([
                'f.id',//ID
                'f.name',//名称
                'f.link',//路径
                'f.size',//大小
                'f.ext',//扩展名
                'f.type',//类型
                'f.mime',//mime
                'f.created_at',//上传时间
            ])
                ->from(['f' => $uploadManager->fileTable])
                ->leftJoin(['c' => $uploadManager->fileClassifyTable], 'f.classify_id=c.id')
                ->filterWhere([
                    'or',
                    ['c.id' => empty($queryParam['classify']) ? null : $queryParam['classify']],
                    ['like', 'c.path', empty($queryParam['classify']) ? null : "-{$queryParam['classify']}-"],
                ])
                ->andFilterWhere(['like', 'f.name', isset($queryParam['keyword']) ? $queryParam['keyword'] : null])
                ->orderBy(['f.id' => SORT_DESC]);
            list($page, $list) = App::resolvePage($query, function ($data) {
                foreach ($data as &$item) {
                    $item['url'] = Url::whole($item['link']);
                    $item['size_short'] = Yii::$app->formatter->asShortSize($item['size'], 2);
                }

                return $data;
            });

            return $this->asOk('success', compact('page', 'list'));
        } else {
            $this->setLayoutPartial();
            return $this->render('index');
        }
    }

    /**
     * 移动文件
     * @return string
     * @throws \Throwable
     */
    public function actionMove()
    {
        $bodyParams = $this->filteredPost;
        $dynamicModel = DynamicModel::validateData($bodyParams, [
            [['name', $this->etagParam, 'classify'], 'required'],
            [['name', $this->etagParam], 'string', 'max' => '255'],
            ['classify', 'integer'],
        ]);
        if ($dynamicModel->hasErrors()) {
            return $this->asFail(current($dynamicModel->firstErrors));
        }

        $etag = $bodyParams[$this->etagParam];
        $tmpPath = $this->tmpDir . $etag;
        if (!is_file($tmpPath)) {
            return $this->asFail('PARAMETER ERROR:文件存在');
        }

        $_FILES[$this->_fileParam] = [
            'name' => $bodyParams['name'],
            'type' => FileHelper::getMimeType($tmpPath, null, false),
            'tmp_name' => $tmpPath,
            'error' => 0,
            'size' => filesize($tmpPath),
        ];

        $result = $this->uploadManager->add($bodyParams['classify'], false);
        @unlink($tmpPath);

        if ($result) {
            return $this->asOk('', $this->uploadManager->result);
        }

        return $this->asFail($this->uploadManager->error);
    }

    /**
     * 分片上传
     * @return string
     * @throws \Throwable
     */
    public function actionChunk()
    {
        if ($this->isAjax) {
            $bodyParams = $this->post;
            if (empty($_FILES)) throw new ErrorException('$_FILES is an empty variable');
            if (empty($bodyParams[$this->etagParam])) throw new ErrorException('The ETag parameter is missing.');

            $file = $this->tmpDir . $bodyParams[$this->etagParam];
            file_put_contents($file, file_get_contents($_FILES[$this->_fileParam]['tmp_name']), FILE_APPEND);

            return null;
        } else {
            $queryParam = $this->filteredGet;
            $uploadManager = $this->uploadManager;
            if (empty($queryParam['classify'])) {
                $classifyId = 0;
                $classifyName = '全部';
            } else {
                $classifyId = $queryParam['classify'];
                $classifyData = $uploadManager->query->select('name')->from($uploadManager->fileClassifyTable)->where(['id' => $classifyId])->one();
                if (empty($classifyData)) {
                    return $this->renderContentX(H5::alert('参数ID错误!'), 'warning');
                }

                $classifyName = $classifyData['name'];
            }

            $this->setLayoutPartial();
            return $this->render('add', [
                'classifyData' => [
                    'id' => $classifyId,
                    'name' => $classifyName,
                ],
            ]);
        }
    }

    /**
     * 断点信息
     * @return \yii\web\Response
     * @throws \Throwable
     */
    public function actionResume()
    {
        $queryParams = $this->get;
        if (empty($queryParams[$this->etagParam])) {
            throw new ErrorException('The ETag parameter is missing.');
        }

        $fileSize = 0;
        $filePath = $this->tmpDir . $queryParams[$this->etagParam];

        if (is_file($filePath)) {
            $fileSize = filesize($filePath);
        }

        return $this->asJson([
            'size' => $fileSize,
        ]);
    }
}